package objects.base;

import objects.base.Rectangle;

import org.newdawn.slick.GameContainer;
import org.newdawn.slick.state.StateBasedGame;
import util.level.Room;


/**
 * A superclass for all rectangles that can move.
 * Doesn't necessitate the ability to do collision handling.
 * 
 * @author John
 */

public class Movable extends Rectangle
{
	protected MotionVector[] xMotion, yMotion;
	protected int vectorTotal;
	public float totalXSpeed = 0, totalYSpeed = 0;
	
	// Decides whether this object can be affected by other moving objects.
	protected boolean hasInertia = true;
	
	/**
	 * Initializes a movable object of given size at the given position.
	 * 
	 * @param initX - The initial X position.
	 * @param initY - The initial Y position.
	 * @param initWidth - The initial width of the object.
	 * @param initHeight - The initial height of the object.
	 */
	public Movable(float initX, float initY, float initWidth, float initHeight)
	{
		// Call super constructor
		super(initX, initY, initWidth, initHeight);
		// Default to having 1 motion vector
		setMotionVectors(1);
	}
	
	/**
	 * Changes the number of motion vectors an object can
	 * keep track of separately from each other and initializes them.
	 * 
	 * (Useful for when speed limiters only apply in some cases,
	 * but not other ones, ie a player getting knocked back much
	 * faster than he could normally move.)
	 * 
	 * @param newVectorTotal - The new number of motion vectors to use.
	 */
	public void setMotionVectors(int newVectorTotal)
	{
		// Set new vector total
		vectorTotal = newVectorTotal;
		// Initialize vector arrays
		xMotion = new MotionVector[vectorTotal];
		yMotion = new MotionVector[vectorTotal];
		// Create new motion vectors
		for (int i = 0; i < vectorTotal; i++)
		{
			xMotion[i] = new MotionVector();
			yMotion[i] = new MotionVector();
		}
	}
	
	/**
	 * Simulates an object's motion forward in time.
	 * 
	 * @param gc {@inheritDoc}
	 * @param game {@inheritDoc}
	 */
	public void step(GameContainer gc, StateBasedGame sbg, Room room)
	{
		// Call parent class behaviour
		super.step(gc,sbg,room);
		
		// Reset speed object was moved by
		totalXSpeed = 0;
		totalYSpeed = 0;
		float partTotalXSpeed = 0;
		float partTotalYSpeed = 0;
		// Iterate through all the vectors
		for (int i = 0; i < vectorTotal; i++)
		{
			// Increment speed values by acceleration
			xMotion[i].speed += xMotion[i].acc;
			yMotion[i].speed += yMotion[i].acc;
			// Tally up total speed
			partTotalXSpeed += xMotion[i].speed;
			partTotalYSpeed += yMotion[i].speed;
		}
		
		if (hasInertia)
		{
			// Adjust speed depending on whether something is carrying the object
			partTotalXSpeed = carryInertiaX(partTotalXSpeed);
			partTotalYSpeed = carryInertiaY(partTotalYSpeed);
		}
		
		// Actually do the movement
		doMotion(room,partTotalXSpeed, partTotalYSpeed);
	}
	
	/**
	 * Takes the given values of the total speed and moves the object forward by exactly those amounts
	 * 
	 * @param moveX - How much to move in the X direction
	 * @param moveY - How much to move in the Y direction
	 */
	public void doMotion(Room room,float moveX, float moveY)
	{
		// Actually do the movement
		if (moveX != 0f)
		{
			totalXSpeed += moveX;
			x = doMoveTo(room,x + moveX, true);
		}
		if (moveY != 0f)
		{
			totalYSpeed += moveY;
			y = doMoveTo(room,y + moveY, false);
		}
	}
	
	/**
	 * Adjusts the speed in the X direction to move relative to anything that is carrying it.
	 * 
	 * @param moveX - How much you were planning to move in the X direction.
	 * @return The amount that the adjusted speed returns.
	 */
	protected float carryInertiaX(float moveX)
	{
		return moveX;
	}
	
	/**
	 * Adjusts the speed in the Y direction to move relative to anything that is carrying it.
	 * 
	 * @return The amount to adjust the speed by.
	 */
	protected float carryInertiaY(float moveY)
	{
		return moveY;
	}
	
	/**
	 * A wrapper method that moves an object as close to
	 * a certain location as possible.
	 * 
	 * (Note: This method does not actually move the object, but rather,
	 * returns the position that it can be moved to.)
	 * 
	 * @param targetPos - The position to try to move to.
	 * @param dimension - The dimension being moved in (TRUE -> X, FALSE -> Y)
	 * @return The actual position that can be moved to.
	 */
	protected float doMoveTo(Room room,float targetPos, boolean dimension)
	{
		return targetPos;
	}

	/**
	 * Wraps around speed and acceleration, multiple motion vectors per
	 * dimension may be used to 
	 */
	protected class MotionVector
	{
		public float speed = 0;
		public float acc = 0;
	}
	
	/**
	 * Returns the X motion vectors of the object.
	 * @return - A reference to the array of X motion vectors.
	 */
	public MotionVector[] getXVectors()
	{
		return xMotion;
	}
	
	/**
	 * Returns the Y motion vectors of the object.
	 * @return - A reference to the array of Y motion vectors.
	 */
	public MotionVector[] getYVectors()
	{
		return yMotion;
	}
}

